package tpase.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import it.unisa.dia.gas.jpbc.Element;
import it.unisa.dia.gas.jpbc.Field;
import it.unisa.dia.gas.jpbc.Pairing;
import it.unisa.dia.gas.plaf.jpbc.pairing.PairingFactory;
import tpase.client.VO.Address;
import tpase.client.VO.MessageCode;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

public class Util {

    /**
     * 向 .properties文件file插入 key = value
     *
     * @param filePath 文件的路径，如 config/parameters.properties
     * @param key      名称
     * @param value    值
     */
    public static void setProperty(String filePath, String key, String value) {
        Properties properties = new Properties();
        try {
            InputStream inputStream = ClassLoader.getSystemResourceAsStream(filePath);
            properties.load(inputStream);
            properties.setProperty(key, value);
            OutputStream outputStream = filePathToFileOutputStream(filePath);
            properties.store(outputStream, "modify");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取 .properties文件中 key 对应的值
     *
     * @param file 文件的路径，如 config/parameters.properties
     * @param key  名称
     * @return 返回对应的值
     */
    public static String getProperty(String file, String key) {
        Properties properties = new Properties();
        String value;
        try {
            InputStream inputStream = ClassLoader.getSystemResourceAsStream(file);
            properties.load(inputStream);
            value = properties.getProperty(key);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return value;
    }

    /**
     * 清空 .properties 文件
     *
     * @param filePath 文件的路径，如 config/parameters.properties
     */
    public static void clearProperty(String filePath) {
        Properties properties = new Properties();
        try {
            InputStream inputStream = ClassLoader.getSystemResourceAsStream(filePath);
            properties.load(inputStream);
            properties.clear();
            OutputStream outputStream = filePathToFileOutputStream(filePath);
            properties.store(outputStream, "modify");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 根据文件路径转换为 FileOutputStream 对象
     *
     * @param filePath 文件的路径，如 config/parameters.properties
     * @return FileOutputStream
     */
    public static FileOutputStream filePathToFileOutputStream(String filePath) {
        URL url = ClassLoader.getSystemResource(filePath);
        try {
            String decodePath = URLDecoder.decode(url.getFile(), "UTF-8");
            return new FileOutputStream(new File(decodePath));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static Pairing getPairing(String path) {
        return PairingFactory.getPairing(path);
    }

    private static Bootstrap getBootstrap(ChannelInboundHandlerAdapter channelInboundHandlerAdapter) {
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.option(ChannelOption.SO_RCVBUF, 1024 * 1024);
        // 设置客户端的通道实现类型
        bootstrap.channel(NioSocketChannel.class);
        // 使用匿名内部类初始化通道
        bootstrap.handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                ChannelPipeline channelPipeline = socketChannel.pipeline();

//                    channelPipeline.addLast(new LoggingHandler(LogLevel.INFO));
                channelPipeline.addLast(new LengthFieldBasedFrameDecoder(1024 * 1024 * 10, 0, 4, 0, 0));
                channelPipeline.addLast(new MessageCode());
                //添加客户端通道的处理器
                channelPipeline.addLast(channelInboundHandlerAdapter);
            }
        });
        return bootstrap;
    }

    public static void processClientBootstrap(ChannelInboundHandlerAdapter channelInboundHandlerAdapter, Address localAddress, Address connectAddress) {
        NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = getBootstrap(channelInboundHandlerAdapter);
            bootstrap.group(eventExecutors);

            // 客户端同一个端口不能同时和多个服务器建立连接
            bootstrap.localAddress(localAddress.getIp(), localAddress.getPort());
            ChannelFuture channelFuture = bootstrap.connect(connectAddress.getIp(), connectAddress.getPort()).sync();
            // 对通道关闭进行监听
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭线程组
            eventExecutors.shutdownGracefully();
        }
    }

    /**
     * 简化输出字符串内容
     *
     * @param str
     * @return
     */
    public static String formatString(String str, int len) {
        // 检查字符串是否长于10个字符
        if (str.length() > 40) {
            // 如果是，截取前5个字符和后5个字符，并用...连接
            return str.substring(0, len) + " ... " + str.substring(str.length() - len);
        }
        // 如果不是，直接返回原字符串
        return str;
    }

    public static String formatString(String str) {
        return formatString(str, 5);
    }

    public static void processClientBootstrap(ChannelInboundHandlerAdapter channelInboundHandlerAdapter, Address connectAddress) {
        NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = getBootstrap(channelInboundHandlerAdapter);
            bootstrap.group(eventExecutors);

            // 客户端同一个端口不能同时和多个服务器建立连接
            ChannelFuture channelFuture = bootstrap.connect(connectAddress.getIp(), connectAddress.getPort()).sync();
            // 对通道关闭进行监听
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭线程组
            eventExecutors.shutdownGracefully();
        }
    }

    public static <T> T jsonToObject(String json, Class<T> cls) {
        ObjectMapper mapper = new ObjectMapper();
        try {
            return mapper.readValue(json, cls);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    public static <T> T jsonToObject(String json, Class<?> parameterized, Class<?>... parameterClasses) {
        ObjectMapper mapper = new ObjectMapper();
        JavaType javaType = mapper.getTypeFactory().constructParametricType(parameterized, parameterClasses);
        try {
            return mapper.readValue(json, javaType);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    public static String toJson(Object obj) {
        ObjectMapper mapper = new ObjectMapper();
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
        try {
            return mapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    public static <T> T jsonFileToObj(String path, Class<T> cls) {
        ObjectMapper mapper = new ObjectMapper();
        T t = null;
        try {
            File f = new File(path);
            FileReader fileReader = new FileReader(f);
            Reader reader = new InputStreamReader(new FileInputStream(f), StandardCharsets.UTF_8);
            int ch = 0;
            StringBuffer sb = new StringBuffer();
            while ((ch = reader.read()) != -1) {
                sb.append((char) ch);
            }
            fileReader.close();
            reader.close();
            String jsonStr = sb.toString();
            t = mapper.readValue(jsonStr, cls);
            //t = mapper.readValue(new File(path), cls);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return t;
    }

    public static byte[] intToByteArr(int value) {
        byte[] idxBytes = new byte[1];
        idxBytes[0] = (byte) value;
        return idxBytes;
    }

    public static void writeJson(String path, Object obj) {
        try {
            String s = toJson(obj);
            BufferedWriter writer = new BufferedWriter(new FileWriter(path));
            writer.write(s);
            writer.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static byte[] hashToBytes(String msg) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            return digest.digest(msg.getBytes(StandardCharsets.UTF_8));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    public static Element hash(Field field, String msg) {
        byte[] hashedData = hashToBytes(msg);
        return field.newElementFromHash(hashedData, 0, hashedData.length);
    }

    public static Element hash(Field field, byte[] bytes) {
        return field.newElementFromHash(bytes, 0, bytes.length);
    }

    /**
     * 合并两个 byte[]
     * @param array1
     * @param array2
     * @return
     */
    public static byte[] mergeArrays(byte[] array1, byte[] array2) {
        // 创建一个新的数组，它的大小是两个原始数组大小的总和
        byte[] mergedArray = new byte[array1.length + array2.length];

        // 使用System.arraycopy方法复制数组
        System.arraycopy(array1, 0, mergedArray, 0, array1.length);
        System.arraycopy(array2, 0, mergedArray, array1.length, array2.length);

        return mergedArray;
    }

    /**
     * 合并两个 byte[]
     * 最终结果  [ byte1.length, byte1, byte2.length, byte2 ]
     * @param array1
     * @param array2
     * @return
     */
    public static byte[] combine(byte[] array1, byte[] array2) {
        ByteBuffer buffer = ByteBuffer.allocate(4 + array1.length + 4 + array2.length);
        buffer.putInt(array1.length);
        buffer.put(array1);
        buffer.putInt(array2.length);
        buffer.put(array2);
        return buffer.array();
    }

    /**
     * 将 combined 拆分成两个 byte[] 分别在 返回值的 [0] 和 [1]
     * @param combined
     * @return
     */
    public static byte[][] split(byte[] combined) {
        ByteBuffer buffer = ByteBuffer.wrap(combined);

        int length1 = buffer.getInt();
        byte[] array1 = new byte[length1];
        buffer.get(array1, 0, length1);

        int length2 = buffer.getInt();
        byte[] array2 = new byte[length2];
        buffer.get(array2, 0, length2);

        return new byte[][]{array1, array2};
    }

    public static Element getLambda_0iT_ByElement(Element x, List<Element> idList, Pairing pairing) {

        Element lambda_0iT = pairing.getZr().newOneElement();
        for (int i = 0; i < idList.size(); i++) {
            Element e_i = idList.get(i);
            if (e_i.equals(x)) {
                continue;
            }
            // (0 - e_i) / (x - e_i)
            Element zero = pairing.getZr().newZeroElement();
            Element up = zero.duplicate().sub(e_i);
            Element down = x.duplicate().sub(e_i);
            lambda_0iT.mulZn(up.duplicate().div(down.duplicate()));
        }

        return lambda_0iT;
    }

    /**
     * 需要计算 Lambda 的插值多项式
     *
     * @param y1iList
     * @param rPwd
     * @param serverXList
     * @param pairing
     * @return
     */
    public static Element getY1(List<Element> y1iList, Element rPwd, List<Element> serverXList, Pairing pairing) {
        Element Y1 = pairing.getGT().newOneElement();
        List<Element> lambdaList = new ArrayList<>();

        for (int i = 0; i < serverXList.size(); i++) {
            lambdaList.add(Util.getLambda_0iT_ByElement(serverXList.get(i), serverXList, pairing));
        }

        for (int i = 0; i < y1iList.size(); i++) {
            Element lambda = lambdaList.get(i);
            Element tmp = y1iList.get(i).duplicate().powZn(lambda);

            Y1.add(tmp);
        }
        Y1.powZn(rPwd.duplicate().invert());
        return Y1;
    }

    /**
     * 不需要计算 Lambda 的插值多项式
     *
     * @param y1iList
     * @param r
     * @param pairing
     * @return
     */
    public static Element getY1iWithoutLambda(List<Element> y1iList, Element r, Pairing pairing) {
        Element Y1 = pairing.getGT().newOneElement();
        for (Element Y_1i : y1iList) {
            Y1.add(Y_1i);
        }
        // e(*, *)^{r^{-1}_{pwd}}
        Y1.powZn(r.duplicate().invert());
        return Y1;
    }

    public static Element getY1iWithoutLambda(String uid, Element x_pwd, Pairing pairing, Element s_i) {
        Element H_2_uid = Util.hash(pairing.getG2(), uid);
        // e(x_pwd, H_2(uid))^{lambda_0iT * s_i}
        return pairing.pairing(x_pwd, H_2_uid).powZn(s_i);
    }

    public static Element getY2(List<Element> y2iList, List<Element> serverXList, Pairing pairing) {
        List<Element> lambdaList = new ArrayList<>();
        for (int i = 0; i < serverXList.size(); i++) {
            lambdaList.add(Util.getLambda_0iT_ByElement(serverXList.get(i), serverXList, pairing));
        }

        Element Y2 = pairing.getG2().newOneElement();
        for (int i = 0; i < y2iList.size(); i++) {
            Y2.add(y2iList.get(i).duplicate().powZn(lambdaList.get(i)));
        }

        return Y2;
    }

    public static void main(String[] args) {
        byte[] array1 = {1, 2, 3};
        byte[] array2 = {4, 5, 6};

        // 将两个数组结合起来
        byte[] combined = combine(array1, array2);

        // 分隔数组
        byte[][] splitArrays = split(combined);

        // 输出以验证
        for (byte b : splitArrays[0]) {
            System.out.print(b + " ");
        }
        System.out.println();

        for (byte b : splitArrays[1]) {
            System.out.print(b + " ");
        }

    }

    public static List<Address> getThresholdList(List<Address> serverList, int t) {
        List<Address> list = new ArrayList<>(serverList);
        // 打乱顺序，n个服务器中任意选t个服务器
        Collections.shuffle(list);
        return list.subList(0, t);
    }

    public static List<byte[]> getIdList(List<Address> thresholdList) {
        List<byte[]> idList = new ArrayList<>();
        for (int i = 0; i < thresholdList.size(); i++) {
            idList.add(thresholdList.get(i).getId());
        }
        return idList;
    }


    public static byte[] decrypt(String key, byte[] input) {
        Cipher cipher = null;
        try {
            cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            byte[] keyBytes = Arrays.copyOf(hashToBytes(key), 16);
            SecretKey keySpec = new SecretKeySpec(keyBytes, "AES");
            cipher.init(Cipher.DECRYPT_MODE, keySpec);
            return cipher.doFinal(input);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static byte[] encrypt(String key, byte[] input) {
        final String SECRET = "AES";
        final String CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
        try {
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            byte[] keyBytes = Arrays.copyOf(hashToBytes(key), 16);
            SecretKey keySpec = new SecretKeySpec(keyBytes, SECRET);
            cipher.init(Cipher.ENCRYPT_MODE, keySpec);
            return cipher.doFinal(input);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void writeBytes(byte[] bytes, String filePath) {
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(filePath);
            fileOutputStream.write(bytes);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    public static byte[] readFileToBytes(String filePath) {
        byte[] data;
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(filePath);
            data = new byte[fileInputStream.available()];
            fileInputStream.read(data);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return data;
    }

    /**
     * 如果目录不存在，则创建目录
     * @param path
     */
    public static void createDirectory(String path) {
        Path folderPath = Paths.get(path);
        // 创建 用户文件夹
        if (!Files.exists(folderPath)) {
            try {
                Files.createDirectories(folderPath);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
